/****************************************************************
 *								*
 *	Copyright 2007, 2008 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include "gtm_string.h"
#include <errno.h>
#include <libelf.h>
#include "gtm_fcntl.h"
#include "gtm_unistd.h"
#include "gtm_stdio.h"

#include "compiler.h"
#include "rtnhdr.h"
#include "obj_gen.h"
#include "cgp.h"
#include "mdq.h"
#include "cmd_qlf.h"
#include "objlabel.h"	/* needed for masscomp.h */
#include "stringpool.h"
#include "parse_file.h"
#include "gtmio.h"
#include "mmemory.h"
#include "obj_file.h"
#include "obj_filesp.h"
#include "release_name.h"
#include "min_max.h"
/* The following definitions are reqquired for the new(for ELF files) create/close_obj_file.c functions */

#ifdef __linux__
#define ELF64_LINKER_FLAG       0x10
#else
#define ELF64_LINKER_FLAG       0x18
#endif /* __linux__ */


/* Platform specific action instructions when routine called from foreign language */
/* Currently just a return to caller on AIX */

#define MIN_LINK_PSECT_SIZE     0
LITDEF mach_inst jsb_action[JSB_ACTION_N_INS] = {0x48, 0xc7, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xc3};


GBLREF command_qualifier cmd_qlf;
GBLREF char		object_file_name[];
GBLREF int		object_file_des;
GBLREF short		object_name_len;
GBLREF mident		module_name;
GBLREF boolean_t	run_time;
GBLREF int4		gtm_object_size;
DEBUG_ONLY(GBLREF int   obj_bytes_written;)

#define GTM_LANG        "MUMPS"
static char static_string_tbl[] = {
        /* Offset 0 */  '\0',
        /* Offset 1 */  '.', 't', 'e', 'x', 't', '\0',
        /* Offset 7 */  '.', 's', 't', 'r', 't', 'a', 'b', '\0',
        /* Offset 15 */ '.', 's', 'y', 'm', 't', 'a', 'b', '\0'
};

#define SPACE_STRING_ALLOC_LEN  (sizeof(static_string_tbl) +    \
                                 sizeof(GTM_LANG) + 1 +         \
                                 sizeof(GTM_PRODUCT) + 1 +      \
                                 sizeof(GTM_RELEASE_NAME) + 1 + \
                                 sizeof(mident_fixed))

/* Following constants has to be in sync with above static string array(static_string_tbl) */
#define STR_SEC_TEXT_OFFSET 1
#define STR_SEC_STRTAB_OFFSET 7
#define STR_SEC_SYMTAB_OFFSET 15

#define SEC_TEXT_INDX 1
#define SEC_STRTAB_INDX 2
#define SEC_SYMTAB_INDX 3

GBLDEF uint4 txtrel_cnt;

LITREF char gtm_release_name[];
LITREF int4 gtm_release_name_len;

GBLREF mliteral 	literal_chain;
GBLREF char 		source_file_name[];
GBLREF unsigned short 	source_name_len;
GBLREF mident	routine_name;
GBLREF mident	module_name;
GBLREF int4	mlmax, mvmax;
GBLREF int4	code_size, lit_addrs, lits_size;
GBLREF int4	psect_use_tab[];	/* bytes of each psect in this module */

static short int current_psect;
static char emit_buff[OBJ_EMIT_BUF_SIZE];	/* buffer for emit output */
static short int emit_buff_used;		/* number of chars in emit_buff */

static uint4 cdlits;
static struct rel_table *data_rel, *data_rel_end;
static struct rel_table *text_rel, *text_rel_end;
static int file_des;
DEBUG_ONLY(static uint4 		txtrel_cnt_in_hdr;)

error_def(ERR_OBJFILERR);

/* Open the object file and write out the gtm object. Actual ELF creation happens at later stage during close_object_file */
void create_object_file(rhdtyp *rhead)
{
        int             status, rout_len;
        char            obj_name[sizeof(mident_fixed) + 5];
        mstr            fstr;
        parse_blk       pblk;

        error_def(ERR_FILEPARSE);

        assert(!run_time);

        DEBUG_ONLY(obj_bytes_written = 0);
        memset(&pblk, 0, sizeof(pblk));
        pblk.buffer = object_file_name;
        pblk.buff_size = MAX_FBUFF;

        /* create the object file */
        fstr.len = (MV_DEFINED(&cmd_qlf.object_file) ? cmd_qlf.object_file.str.len : 0);
        fstr.addr = cmd_qlf.object_file.str.addr;
        rout_len = (int)module_name.len;
        memcpy(&obj_name[0], module_name.addr, rout_len);
        memcpy(&obj_name[rout_len], DOTOBJ, sizeof(DOTOBJ));    /* includes null terminator */
        pblk.def1_size = rout_len + sizeof(DOTOBJ) - 1;         /* Length does not include null terminator */
        pblk.def1_buf = obj_name;
        status = parse_file(&fstr, &pblk);
        if (0 == (status & 1))
                rts_error(VARLSTCNT(5) ERR_FILEPARSE, 2, fstr.len, fstr.addr, status);

        object_name_len = pblk.b_esl;
        object_file_name[object_name_len] = 0;

        OPEN_OBJECT_FILE(object_file_name, O_CREAT | O_RDWR, object_file_des);
        if (-1 == object_file_des)
                rts_error(VARLSTCNT(5) ERR_OBJFILERR, 2, object_name_len, object_file_name, errno);

/* Action instructions and marker are not kept in the same array since the type of the elements of
 * the former (uint4) may be different from the type of the elements of the latter (char).
 * 'tiz cleaner this way rather than converting one to the other type in order to be accommodated
 * in an array
 * */
        assert(JSB_ACTION_N_INS * sizeof(jsb_action[0]) == sizeof(jsb_action)); /* JSB_ACTION_N_INS maintained? */
        assert(sizeof(jsb_action) <= sizeof(rhead->jsb));                       /* overflow check */

  	memcpy(rhead->jsb, (char *)jsb_action, sizeof(jsb_action)); /* action instructions */
        memcpy(&rhead->jsb[sizeof(jsb_action)], JSB_MARKER,                     /* followed by GTM_CODE marker */
               MIN(STR_LIT_LEN(JSB_MARKER), sizeof(rhead->jsb) - sizeof(jsb_action)));

        emit_immed((char *)rhead, sizeof(*rhead));
}


/* At this point, we know only gtm_object has been written onto the file.
 *  * Read that gtm_object and wrap it up in .text section, add remaining sections to native object(ELF)
 *   * Update the ELF, write it out to the object file and close the object file */
void close_object_file(void)
{
        int		status;
        size_t		bufSize;
        ssize_t		actualSize;
        char		*gtm_obj_code, *string_tbl;
        int		symIndex, strEntrySize;
        Elf		*elf;
        Elf64_Ehdr	*ehdr;
        Elf64_Shdr	*shdr, *text_shdr, *symtab_shdr, *strtab_shdr;
        Elf_Scn		*text_scn, *symtab_scn, *strtab_scn;
        Elf_Data	*text_data, *symtab_data, *strtab_data;

        buff_flush();
        bufSize = gtm_object_size;
        actualSize = 0;
        string_tbl = malloc(SPACE_STRING_ALLOC_LEN);
        symIndex = 0;

        strEntrySize = sizeof(static_string_tbl);
        memcpy((string_tbl + symIndex), static_string_tbl, strEntrySize);
        symIndex += strEntrySize;

        strEntrySize = sizeof(GTM_LANG);
        memcpy((string_tbl + symIndex), GTM_LANG, strEntrySize);
        symIndex += strEntrySize;

        strEntrySize = sizeof(GTM_PRODUCT);
        memcpy((string_tbl + symIndex), GTM_PRODUCT, strEntrySize);
        symIndex += strEntrySize;

        strEntrySize = sizeof(GTM_RELEASE_NAME);
        memcpy((string_tbl + symIndex), GTM_RELEASE_NAME, strEntrySize);
        symIndex += strEntrySize;

        gtm_obj_code = (char *)malloc(bufSize);
        /* At this point, we have only the GTM object written onto the file. We need to read it back and wrap inside 
	   the ELF object and write a native ELF object file.
	*/
        lseek(object_file_des, 0, SEEK_SET);
        DOREADRL(object_file_des, gtm_obj_code, bufSize, actualSize);
        /* Reset the pointer back for writing an ELF object. */
        lseek(object_file_des, 0, SEEK_SET);

        /* Generate ELF64 header */
        if (elf_version(EV_CURRENT) == EV_NONE )
        {
		FPRINTF(stderr, "Elf library out of date!n");
		GTMASSERT;
        }

        if ((elf = elf_begin(object_file_des, ELF_C_WRITE, NULL)) == 0)
        {
		FPRINTF(stderr, "elf_begin failed!\n");
		GTMASSERT;
        }

        if ( (ehdr = elf64_newehdr(elf)) == NULL )
        {
		FPRINTF(stderr, "elf64_newehdr() failed!\n");
		GTMASSERT;
        }

        ehdr->e_ident[EI_MAG0] = ELFMAG0;
        ehdr->e_ident[EI_MAG1] = ELFMAG1;
        ehdr->e_ident[EI_MAG2] = ELFMAG2;
        ehdr->e_ident[EI_MAG3] = ELFMAG3;
        ehdr->e_ident[EI_CLASS] = ELFCLASS64;
        ehdr->e_ident[EI_VERSION] = EV_CURRENT;
#ifdef __hpux
        ehdr->e_ident[EI_DATA] = ELFDATA2MSB;
        ehdr->e_ident[EI_OSABI] = ELFOSABI_HPUX;
#else
        ehdr->e_ident[EI_DATA] = ELFDATA2LSB;
        ehdr->e_ident[EI_OSABI] = ELFOSABI_LINUX;
#endif /* __hpux */
        ehdr->e_ident[EI_ABIVERSION] = EV_CURRENT;
        ehdr->e_machine = EM_X86_64;
        ehdr->e_type = ET_REL;
        ehdr->e_version = EV_CURRENT;
        ehdr->e_shoff = sizeof(Elf64_Ehdr);
        ehdr->e_flags = ELF64_LINKER_FLAG;

        if ((text_scn = elf_newscn(elf)) == NULL)
        {
                FPRINTF(stderr, "elf_newscn() failed for text section!\n");
                GTMASSERT;
        }

        if ((text_data = elf_newdata(text_scn)) == NULL)
        {
                FPRINTF(stderr, "elf_newdata() failed for text section!\n");
                GTMASSERT;
        }

        text_data->d_align = SECTION_ALIGN_BOUNDARY;
        text_data->d_off  = 0LL;
        text_data->d_buf  = gtm_obj_code;
        text_data->d_type = ELF_T_REL;
        text_data->d_size = gtm_object_size;
        text_data->d_version = EV_CURRENT;

        if ((text_shdr = elf64_getshdr(text_scn)) == NULL)
        {
                FPRINTF(stderr, "elf64_getshdr() failed for text section\n");
                GTMASSERT;
        }

        text_shdr->sh_name = STR_SEC_TEXT_OFFSET;
        text_shdr->sh_type = SHT_PROGBITS;
        text_shdr->sh_flags = SHF_ALLOC | SHF_EXECINSTR;
        text_shdr->sh_entsize = gtm_object_size;

        memcpy((string_tbl +  symIndex), module_name.addr, module_name.len);
        string_tbl[symIndex + module_name.len] = '\0';

        if ((strtab_scn = elf_newscn(elf)) == NULL)
        {
                FPRINTF(stderr, "elf_newscn() failed for strtab section\n");
                GTMASSERT;
        }

        if ((strtab_data = elf_newdata(strtab_scn)) == NULL)
        {
                FPRINTF(stderr, "elf_newdata() failed for strtab section!\n");
                GTMASSERT;
        }

        strtab_data->d_align = NATIVE_WSIZE;
        strtab_data->d_buf = string_tbl;
        strtab_data->d_off = 0LL;
        strtab_data->d_size = SPACE_STRING_ALLOC_LEN;
        strtab_data->d_type = ELF_T_BYTE;
        strtab_data->d_version = EV_CURRENT;

        if ((strtab_shdr = elf64_getshdr(strtab_scn)) == NULL)
        {
                FPRINTF(stderr, "elf_getshdr() failed for strtab section!\n");
                GTMASSERT;
        }

        strtab_shdr->sh_name = STR_SEC_STRTAB_OFFSET;
        strtab_shdr->sh_type = SHT_STRTAB;
        strtab_shdr->sh_entsize = 0;
        ehdr->e_shstrndx = elf_ndxscn(strtab_scn);

        /* Creating .symbtab section */
        int i = 0;
        Elf64_Sym symEntries[3];

        /* NULL symbol */
        symEntries[i].st_name = 0;
        symEntries[i].st_info = ELF64_ST_INFO(STB_LOCAL, STT_NOTYPE);
        symEntries[i].st_other = STV_DEFAULT;
        symEntries[i].st_shndx = 0;
        symEntries[i].st_size = 0;
        symEntries[i].st_value = 0;
        i++;

        /* Module symbol */
        symEntries[i].st_name = symIndex;
        symEntries[i].st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC);
        symEntries[i].st_other = STV_DEFAULT;
        symEntries[i].st_shndx = SEC_TEXT_INDX;
        symEntries[i].st_size = gtm_object_size;
        symEntries[i].st_value = 0;
        i++;

        /* symbol for .text section */
        symEntries[i].st_name = STR_SEC_TEXT_OFFSET;
        symEntries[i].st_info = ELF64_ST_INFO(STB_LOCAL, STT_SECTION);
        symEntries[i].st_other = STV_DEFAULT;
        symEntries[i].st_shndx = SEC_TEXT_INDX; /* index of the .text */
        symEntries[i].st_size = 0;
        symEntries[i].st_value = 0;
        i++;

        if ((symtab_scn = elf_newscn(elf)) == NULL)
        {
                FPRINTF(stderr, "elf_newscn() failed for symtab section!\n");
                GTMASSERT;
        }

        if ((symtab_data = elf_newdata(symtab_scn)) == NULL)
        {
                FPRINTF(stderr, "elf_newdata() failed for symtab section!\n");
                GTMASSERT;
        }

        symtab_data->d_align = NATIVE_WSIZE;
        symtab_data->d_off  = 0LL;
        symtab_data->d_buf  = symEntries;
        symtab_data->d_type = ELF_T_REL;
        symtab_data->d_size = sizeof(Elf64_Sym) * i;
        symtab_data->d_version = EV_CURRENT;

        if ((symtab_shdr = elf64_getshdr(symtab_scn)) == NULL)
        {
                FPRINTF(stderr, "elf_getshdr() failed for symtab section!\n");
                GTMASSERT;
        }

        symtab_shdr->sh_name = STR_SEC_SYMTAB_OFFSET;
        symtab_shdr->sh_type = SHT_SYMTAB;
        symtab_shdr->sh_entsize = sizeof(Elf64_Sym) ;
        symtab_shdr->sh_link = SEC_STRTAB_INDX;

        elf_flagehdr(elf, ELF_C_SET, ELF_F_DIRTY);
        if (elf_update(elf, ELF_C_WRITE) < 0)
        {
                FPRINTF(stderr, "elf_update() failed!\n");
                GTMASSERT;
        }

        elf_end(elf);

        /* Free the memory malloc'ed above */
        free(string_tbl);
        free(gtm_obj_code);

	if ((off_t)-1 == lseek(object_file_des, (off_t)0, SEEK_SET))
		rts_error(VARLSTCNT(5) ERR_OBJFILERR, 2, object_name_len, object_file_name, errno);
}

